package cn.sytton.taffe.web.flw.service.impl;

import cn.sytton.taffe.framework.base.entity.PageParam;
import cn.sytton.taffe.framework.base.service.BaseService;
import cn.sytton.taffe.web.flw.entity.FlowDefEntity;
import cn.sytton.taffe.web.flw.mapper.FlowDefMapper;
import cn.sytton.taffe.web.flw.service.IFlowDefService;
import com.github.pagehelper.PageInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.repository.Resource;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.UserTask;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程定义 服务层
 *
 * @author skyrock
 */
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class FlowDefService extends BaseService<FlowDefMapper, FlowDefEntity> implements IFlowDefService {

    private final CamundaService camundaService;

    @Override
    public void deploy(FlowDefEntity entity) {
        camundaService.repository().createDeployment()
                .addString(entity.getKey()+".bpmn", entity.getXml())
                .name(entity.getName())
                .deploy();
    }

    @Override
    public String findXml(String deploymentId) {
        List<Resource> resourceList = camundaService.repository().getDeploymentResources(deploymentId);
        if (resourceList.size() > 0) {
            return new String(resourceList.get(0).getBytes());
        } else {
            return "";
        }
    }

    @Override
    public String findXmlByKey(String key) {
        ProcessDefinition pDef = camundaService.repository().createProcessDefinitionQuery()
                .processDefinitionKey(key)
                .latestVersion()
                .singleResult();
        if (pDef != null) {
            return findXml(pDef.getDeploymentId());
        } else {
            return "";
        }
    }

    @Override
    public Map<String, List<FlowDefEntity>> findDefList() {
        List<ProcessDefinition> list = camundaService.repository().createProcessDefinitionQuery()
                .orderByDeploymentTime()
                .desc()
                .list();

        List<FlowDefEntity> newList = list.stream()
                .map(p -> new FlowDefEntity()
                        .setKey(p.getKey())
                        .setName(p.getName())
                        .setDefinitionId(p.getId())
                        .setDeploymentId(p.getDeploymentId())
                        .setVersion(p.getVersion()))
                .collect(Collectors.toList());

        return newList.stream()
                .collect(Collectors.groupingBy(FlowDefEntity::getKey));
    }

    @Override
    public List<FlowDefEntity> findVersionListByKey(String key) {
        List<ProcessDefinition> list = camundaService.repository().createProcessDefinitionQuery()
                .processDefinitionKey(key)
                .orderByProcessDefinitionVersion()
                .desc()
                .list();
        return list.stream()
                .map(p -> new FlowDefEntity()
                        .setDefinitionId(p.getId())
                        .setDeploymentId(p.getDeploymentId())
                        .setVersion(p.getVersion()))
                        .collect(Collectors.toList());
    }

    @Override
    public PageInfo<FlowDefEntity> findDefPage(PageParam pp) {
        PageInfo<FlowDefEntity> page = new PageInfo<>();

        long total = camundaService.repository().createProcessDefinitionQuery().active().count();
        page.setTotal(total);

        if (total > 0) {
            int start = pp.getPageNum() == 1 ? 0 : pp.getPageNum();
            int end = start + pp.getPageSize();
            List<ProcessDefinition> list = camundaService.repository().createProcessDefinitionQuery()
                    .latestVersion()
                    .orderByDeploymentTime()
                    .asc()
                    .listPage(start, end);
            List<FlowDefEntity> flowList = list.stream().map(p -> new FlowDefEntity()
                            .setKey(p.getKey())
                            .setName(p.getName())
                            .setVersion(p.getVersion()))
                    .collect(Collectors.toList());
            page.setList(flowList);
        }

        return page;
    }

    @Override
    public Boolean checkKey(String key) {
        return camundaService.repository().createProcessDefinitionQuery()
                .processDefinitionKey(key).count() > 0;
    }

    @Override
    public Boolean checkName(String name) {
        return camundaService.repository().createProcessDefinitionQuery()
                .processDefinitionName(name).count() > 0;
    }

    @Override
    public List<FlowUserTask> findUserTaskByDefKey(String key) {
        ProcessDefinition pd = camundaService.repository().createProcessDefinitionQuery().processDefinitionKey(key).latestVersion().singleResult();
        if (!Objects.isNull(pd)) {
            return findUserTaskByDefId(pd.getId());
        }
        return new ArrayList<>();
    }

    @Override
    public List<FlowUserTask> findUserTaskByDefId(String id) {
        BpmnModelInstance bm = camundaService.repository().getBpmnModelInstance(id);
        Collection<UserTask> tasks = bm.getModelElementsByType(UserTask.class);
        return tasks.stream()
                .map(t -> {
                    boolean first = t.getPreviousNodes().list()
                            .stream()
                            .anyMatch(n -> n.getElementType().getTypeName().equals("startEvent"));
                    return new FlowUserTask(t.getId(), t.getName(), first ? 0 : 1);
                })
                .sorted(Comparator.comparing(FlowUserTask::getOrder))
                .collect(Collectors.toList());
    }

    @Data
    @AllArgsConstructor
    public static class FlowUserTask {
        private String id;
        private String name;
        private int order;
    }
}
